import re
import sys
import openpyxl
import os.path
import glob
import csv
import glob
import os
import sys
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtCore import *
import csv
import chardet

def app_path():
    if hasattr(sys, 'frozen'):  
        return os.path.abspath(sys.executable)
    return os.path.abspath(__file__) 

class OpenFolderWidget(QWidget):
    def __init__(self, title: str):
        super().__init__()
        self.label = QLabel(title)
        self.edit = QLineEdit()
        self.button_open = QPushButton('open folder')
        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.edit)
        self.layout.addWidget(self.button_open)
        self.edit.setReadOnly(True)

class OpenExcelWidget(QWidget):
    def __init__(self, title: str):
        super().__init__()
        self.label = QLabel(title)
        self.edit = QLineEdit()
        self.button_open = QPushButton('open excel')
        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.edit)
        self.layout.addWidget(self.button_open)
        self.edit.setReadOnly(True)

class ProgressBarWidget(QWidget):
    def __init__(self, title: str):
        super().__init__()
        self.label = QLabel(title)
        self.button_start = QPushButton('start')
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.progress_bar)
        self.layout.addWidget(self.button_start)

class CodeCheckView(QWidget):
    signal_folder = Signal(str)
    signal_excelfile = Signal(str)
    signal_currentfile = Signal(str)
    def __init__(self):
        super().__init__()
        self.gtr_workbook_events = None
        self.code_path = OpenFolderWidget('Code path:   ')
        self.excel_path = OpenExcelWidget('Excel path:    ')
        self.progress = ProgressBarWidget('Progress bar:')
        input_layout = QVBoxLayout()
        input_layout.addWidget(self.code_path)
        input_layout.addWidget(self.excel_path)
        input_layout.addWidget(self.progress)
        group_box_input = QGroupBox('')
        group_box_input.setLayout(input_layout)
        tab_layout = QVBoxLayout()
        tab_layout.addWidget(group_box_input)
        self.setLayout(tab_layout)
        self.setWindowTitle("Code check")
        self.resize(600, 300)
        self.show()

        self.code_path.button_open.clicked.connect(self.SlotChooseFolder)
        self.excel_path.button_open.clicked.connect(self.SlotChooseExcelFile)
        self.progress.button_start.clicked.connect(self.TraversalFolder)

    def ProgressBar(self, check_process): 
        self.progress.progress_bar.setValue(check_process)
        QCoreApplication.processEvents()

    def SlotChooseFolder(self): # open folder
        folder_path = QFileDialog.getExistingDirectory(self)
        self.code_path.edit.setText(folder_path)
        self.signal_folder = folder_path
    
    def SlotChooseExcelFile(self): # open excel
        file_path = QFileDialog.getOpenFileName(self)
        self.excel_path.edit.setText(file_path[0])
        self.signal_excelfile = file_path[0]

    def TraversalFolder(self): # start
        folder_path = self.signal_folder
        excel_path = self.signal_excelfile
        main(excel_path, folder_path) # guofeng
        for filename in glob.glob(f'{folder_path}/**/*',recursive=True):
            if ".cpp" in filename:
                self.signal_currentfile = filename
                print(self.signal_currentfile)
                # main(excel_path, folder_path) # guofeng
            self.ProgressBar((glob.glob(f'{folder_path}/**/*',recursive=True).index(filename)+1)/len(glob.glob(f'{folder_path}/**/*',recursive=True)) * 100)

def export_to_csv(data, filename):
    with open(filename, 'a', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerows(data)

def get_cpp_h_file_list(src_path):
    list_cpp_file = []
    list_h_file = []
    file_list = []
    for filepath in glob.glob(src_path + "/**/*", recursive=True):
        if ".cpp" in filepath:
            list_cpp_file.append(filepath)
        elif ".h" in filepath:
            list_h_file.append(filepath)
    for cpp_ame in list_cpp_file:
        h_name= cpp_ame.replace(".cpp",".h")
        if h_name in list_h_file:
            file_dict = []
            file_dict.append(cpp_ame)
            file_dict.append(h_name)
            file_list.append(file_dict)

    return file_list


def read_excel_definition(file_path):
    wb = openpyxl.load_workbook(file_path)
    # sheet = wb.active
    sheet = wb["API Mapping"]

    data_list = []
    structs = {}
    start_reading = False
    for row in sheet.iter_rows(values_only=True):
        members = {}
        row_values = list(row)
        is_empty = all(cell is None or cell == '' for cell in row_values)

        if not start_reading:
            if not is_empty:
                start_reading = True
                # data_list.append(row_values)
        else:
            if is_empty:
                break
            if row_values[1] and "TYPE" in row_values[1]:
                api_name = "R_UC_" + row_values[1]
                if "&DrawData" in row_values[3]:
                    start_index = row_values[3].find('"') + 1
                    end_index = row_values[3].rfind('"')
                    extracted_data = row_values[3][start_index:end_index]
                    members['param1'] = extracted_data
                else:
                    members['param1'] = "none"

                if "Not used" == row_values[4]:
                    members['param2'] = "R_NULL"
                elif "&DrawData" in row_values[4]:
                    start_index = row_values[4].find('"') + 1
                    end_index = row_values[4].rfind('"')
                    extracted_data = row_values[4][start_index:end_index]
                    members['param2'] = extracted_data
                elif "value" in row_values[4]:
                    members['param2'] = "R_" + re.sub(r'value', '', row_values[4], flags=re.IGNORECASE).strip()
                else:
                    members['param2'] = "none"

                if "Not used" == row_values[5]:
                    members['param3'] = "R_NULL"
                elif "&DrawData" in row_values[5]:
                    start_index = row_values[5].find('"') + 1
                    end_index = row_values[5].rfind('"')
                    extracted_data = row_values[5][start_index:end_index]
                    members['param3'] = extracted_data
                elif "value" in row_values[5]:
                    members['param3'] = "R_" + re.sub(r'value', '', row_values[5], flags=re.IGNORECASE).strip()
                else:
                    members['param3'] = "none"
            else:
                continue
            structs[api_name] = members
    # print(structs)
    return structs

def parse_cpp_file(cpp_path):
    """Parse the C++ file and extract the DrawTable two-dimensional array"""
    list1 = []
    with open(cpp_path, 'rb') as f:
        raw_data = f.read()
        result = chardet.detect(raw_data)
        encoding = result['encoding']
    with open(cpp_path, 'r', encoding=encoding) as f:
        content = f.read()

    pattern = r'const\s+st_TYPE\s+K_FUNC_NODE_\w*\s*\[\s*\]\s*=\s*{([\s\S]*?)};'
    match = re.search(pattern, content)
    if not match:
        print(f"Not found K_FUNC_NODE in {cpp_path}")
        return list1

    init_content = re.sub(r'//.*', '', match.group(1))
    init_content = re.sub(r'/\*.*?\*/', '', init_content, flags=re.DOTALL)

    for sub_match in re.finditer(r'{([^{}]*)}', init_content):
        cpp_param = {}
        elements = [e.strip() for e in sub_match.group(1).split(',') if e.strip()]
        if elements:
            cpp_param['api_name'] = elements[4]
            cpp_param['param1'] = elements[1]
            cpp_param['param2'] = elements[2]
            cpp_param['param3'] = elements[3]
            list1.append(cpp_param)
    return list1

def parse_header_file(header_path):
    """Struct information in preparsed header files"""
    structs = {}
    with open(header_path, 'rb') as f:
        raw_data = f.read()
        result = chardet.detect(raw_data)
        encoding = result['encoding']
    with open(header_path, 'r', encoding=encoding) as f:
        content = f.read()

    struct_pattern = r'typedef\s+struct\s*{([\s\S]*?)}\s*st_(\w+)\s*;'
    matches = re.findall(struct_pattern, content)
    if not matches:
       print(f"Struct not found in {header_path}")
       return structs
    for res in matches:
        struct_name=res[1]
        init_content = re.sub(r'//.*', '', res[0])
        init_content = re.sub(r'/\*.*?\*/', '', init_content, flags=re.DOTALL)
        init_content = re.sub(r'^\s*$\n', '', init_content, flags=re.MULTILINE)
        members = {}
        for line in init_content.split(';'):
            line = line.strip()
            member_match = re.match(r'^\s*((?:const\s+)?[\w:<>]+(?:\s*[*&]+\s*)?)\s+(\w+)\s*(?:\[.*?\])?\s*$',line)
            if member_match:
                var_type = member_match.group(1).strip()
                var_name = member_match.group(2).strip()
                members[var_name] = var_type
        structs[struct_name] = members
        # print(f"struct_name:{struct_name}")
        # print(f"struct_name:{structs[struct_name]}")
    return structs

def get_type_compare(list1, structs, dict1, public_macro, error_file, cpp_file_path):
    """Process data and look up type information"""
    #print(f"list1 is {list1}")
    for res in list1:
        api_name = res['api_name']
        # print(f"api_name is {api_name}")
        if api_name in dict1:
            dict1_type = dict1[api_name]["param1"]
            if "none" != dict1_type:
                param1 = res['param1']
                if '.' in param1:
                    matches = re.match(r'\(UL\)&(\w+)\.(\w+)$', param1)
                    if matches:
                        struct_name = matches.group(1)
                        member_name = matches.group(2)
                        if struct_name not in structs:
                            print(f"ERROR : {struct_name} not found !!!")
                        else:
                            if member_name not in structs[struct_name]:
                                print(f"ERROR : {member_name}.{struct_name} not found !!!")
                            else:
                                if structs[struct_name][member_name] != dict1_type:
                                    data_tmp = "The defined types of [" + struct_name + "." + member_name + "] and [" + api_name + "] are inconsistent."
                                    data = [[cpp_file_path, data_tmp]]
                                    # print(f"{data}")
                                    export_to_csv(data,error_file)
                else:
                    if param1.endswith("_TBL"):
                        continue


            dict1_type = dict1[api_name]["param2"]
            if "none" != dict1_type:
                param2 = res['param2']
                if '.' in param2:
                    matches = re.match(r'\(UL\)&(\w+)\.(\w+)$', param2)
                    if matches:
                        struct_name = matches.group(1)
                        member_name = matches.group(2)
                        if struct_name not in structs:
                            print(f"ERROR : {struct_name} not found !!!")
                        else:
                            if member_name not in structs[struct_name]:
                                print(f"ERROR : {member_name}.{struct_name} not found !!!")
                            else:
                                if structs[struct_name][member_name] != dict1_type:
                                    data_tmp = "The defined types of [" + struct_name + "." + member_name + "] and [" + api_name + "] are inconsistent."
                                    data = [[cpp_file_path, data_tmp]]
                                    # print(f"{data}")
                                    export_to_csv(data,error_file)
                else:
                    if param2.endswith("_TBL"):
                        continue
                    if param2 != "R_NULL":
                        if dict1_type not in param2:
                            if param2 in public_macro:
                                if public_macro[param2] != dict1_type:
                                    data_tmp = "The defined types of [" + param2 + "] and [" + api_name + "] are inconsistent."
                                    data = [[cpp_file_path, data_tmp]]
                                    # print(f"{data}")
                                    export_to_csv(data,error_file)
                    else:
                        if param2 != dict1_type:
                            data_tmp = "The defined types of [" + param2 + "] and [" + api_name + "] are inconsistent."
                            data = [[cpp_file_path, data_tmp]]
                            # print(f"{data}")
                            export_to_csv(data,error_file)

            dict1_type = dict1[api_name]["param3"]
            if dict1_type != "none":
                param3 = res['param3']
                if '.' in param3:
                    matches = re.match(r'\(UL\)&(\w+)\.(\w+)$', param3)
                    if matches:
                        struct_name = matches.group(1)
                        member_name = matches.group(2)
                        if struct_name not in structs:
                            print(f"ERROR : {struct_name} not found !!!")
                        else:
                            if member_name not in structs[struct_name]:
                                print(f"ERROR : {member_name}.{struct_name} not found !!!")
                            else:
                                if structs[struct_name][member_name] !=dict1_type:
                                    data_tmp = "The defined types of [" + struct_name + "." + member_name + "] and [" + api_name + "] are inconsistent."
                                    data = [[cpp_file_path, data_tmp]]
                                    # print(f"{data}")
                                    export_to_csv(data,error_file)
                else:
                    if param3.isdigit():
                        continue
                    if param3.endswith("_TBL"):
                        continue
                    if param3 != "R_NULL":
                        if dict1_type not in param3:
                            if param3 in public_macro:
                                if public_macro[param3] != dict1_type:
                                    data_tmp = "The defined types of [" + param3 + "] and [" + api_name + "] are inconsistent."
                                    data = [[cpp_file_path, data_tmp]]
                                    # print(f"{data}")
                                    export_to_csv(data,error_file)
                    else:
                        if param3 != dict1_type:
                            data_tmp = "The defined types of [" + param3 + "] and [" + api_name + "] are inconsistent."
                            data = [[cpp_file_path, data_tmp]]
                            # print(f"{data}")
                            export_to_csv(data,error_file)


def load_public_macro(src_path):
    if os.path.isdir(src_path):
        file_name_dict = {}
        members = {}
        files = os.listdir(src_path)
        h_files = [file for file in files if file.endswith('.h') and os.path.isfile(os.path.join(src_path, file))]
        for file in h_files:
            # print (os.path.join(src_path, file))
            with open(os.path.join(src_path, file), 'rb') as f:
                raw_data = f.read()
                result = chardet.detect(raw_data)
                encoding = result['encoding']
            with open(os.path.join(src_path, file), 'r', encoding=encoding) as f:
                for line in f:
                    if line.startswith('#define'):
                        new_line = re.sub(r'\s+', ' ', line)
                        new_line1 = re.sub(r'\\t+', ' ', new_line)
                        new_line2 = new_line1.replace("#define ","").strip().split()
                        if len(new_line2) == 2:
                            members[new_line2[0]] = new_line2[1]
                            # print (new_line2)
    return members


def main(excel_path, src_path):
    try:
        current_directory = os.getcwd()
        error_file = os.path.join(current_directory, "check_multi_structs_error.csv")
        dict1 = read_excel_definition(excel_path)
        # print(f"Read {len(dict1)} lines of data")

        file_list = get_cpp_h_file_list(src_path)

        public_macro=load_public_macro(src_path)
        # print(f" {public_macro} ")
        #print(file_list)
        for res in file_list:
            list1 = parse_cpp_file(res[0])
            # print(f"Read {len(list1)} lines of data")

            #print(res['h'])
            if len(list1) > 0 :
                structs = parse_header_file(res[1])
                # print(f"FindDiscovered {len(structs)} structs")
                if len(structs) > 0:
                    get_type_compare(list1, structs, dict1, public_macro, error_file, res[0])
        
    except Exception as e:
        print(f"Error : {str(e)}")
        sys.exit(1)


if __name__ == "__main__":
    current_directory = os.getcwd()
    error_file = os.path.join(current_directory, "check_multi_structs_error.csv")
    if os.path.exists(error_file):
        os.remove(error_file)
    data = [["CPP file path", "Error description"]]
    # print(f"{data}")
    export_to_csv(data,error_file)

    app = QApplication([])
    window = CodeCheckView()
    app.exec()